package com.ficus.road.maintain.operationlog.aspect;

import com.alibaba.fastjson.JSON;
import com.ficus.road.maintain.core.model.OperationLog;
import com.ficus.road.maintain.dao.OperationLogDao;
import com.ficus.road.maintain.operationlog.annotation.OpLog;
import com.ficus.road.maintain.strategy.operationlog.OperationLogContext;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;


/**
 * @author leon
 * @version v1.0
 * @createDate 2018年6月6日 上午10:33:35
 * @classRemarks 日志切面类
 */
@Aspect
@Component
@Slf4j
public class OpLogAspect {

    private static final ThreadLocal<Date> beginTimeThreadLocal = new NamedThreadLocal<>("ThreadLocal beginTime");
    private static final ThreadLocal<OperationLog> logThreadLocal = new NamedThreadLocal<>("ThreadLocal log");

    @Autowired(required = false)
    HttpServletRequest request;

    @Autowired
    OperationLogDao operationLogDao;

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param joinPoint 切点
     * @return discription
     */
    public static String getControllerMethodDescription2(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        OpLog opLog = method.getAnnotation(OpLog.class);
        String description = opLog.description();
        return description;
    }

//    public HttpServletRequest getRequest() {
//        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
//        return request;
//    }

    /**
     * Controller层切点 注解拦截
     */
    @Pointcut("@annotation(com.ficus.road.maintain.operationlog.annotation.OpLog)")
    public void logAspect() {
    }

    /**
     * 前置通知 用于拦截Controller层记录用户的操作的开始时间
     *
     * @param joinPoint 切点
     * @throws InterruptedException
     */
    @Before("logAspect()")
    public void doBefore(JoinPoint joinPoint) throws InterruptedException {
        log.info("进入日志切面前置通知!!");
        Date beginTime = new Date();
        beginTimeThreadLocal.set(beginTime); //线程绑定变量（该数据只有当前请求的线程可见）
//        HttpServletRequest request = getRequest();
        if (log.isDebugEnabled()) { //这里日志级别为debug
            log.debug("开始计时: {} URI: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")
                    .format(beginTime), "定时任务，不存在URI");
        }

    }

    /**
     * 后置通知 用于拦截Controller层记录用户的操作
     *
     * @param joinPoint 切点
     */
    @After("logAspect()")
    public void doAfter(JoinPoint joinPoint) {
        log.info("进入日志切面后置通知!!");
        String title = "";
        String type = "info"; //日志类型(info:入库,error:错误)
        Object[] args = joinPoint.getArgs();
        String paramsString = JSON.toJSONString(args);
        try {
            title = getControllerMethodDescription2(joinPoint);
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 打印JVM信息。
        long beginTime = beginTimeThreadLocal.get().getTime();//得到线程绑定的局部变量（开始时间）
        long endTime = System.currentTimeMillis(); //2、结束时间

        log.info("设置日志信息存储到表中!!");
        OperationLog operationLog = new OperationLog();
        operationLog.setTitle(title);
        operationLog.setType(type);
        operationLog.setParams(paramsString);
        Date operateDate = beginTimeThreadLocal.get();
        operationLog.setOperateDate(operateDate);
        operationLog.setTimeout(endTime - beginTime);
        // 处理参数
        try {
            process(joinPoint, paramsString, operationLog, false);
        } catch (Exception e) {
            log.info("OpLogAspect, 处理参数失败");
            e.printStackTrace();
        }
        logThreadLocal.set(operationLog);
    }

    @AfterReturning(returning = "res", pointcut = "logAspect()")
    public void doAfterReturning(JoinPoint joinPoint, Object res) throws Throwable {
        // 处理完请求，返回内容
        log.info("==========返回参数日志=========");
        log.info("返回接口响应参数:" + JSON.toJSONString(res));
        OperationLog operationLog = logThreadLocal.get();
        if (operationLog != null) {
            String returnString = JSON.toJSONString(res);
            operationLog.setResultParams(returnString);
            // 处理返回值
            try {
                process(joinPoint, returnString, operationLog, true);
            } catch (Exception e) {
                log.info("OpLogAspect, 处理返回值失败");
                e.printStackTrace();
            }
            new UpdateLogThread(operationLog, operationLogDao).start();
        }
    }

    /**
     * 异常通知 记录操作报错日志
     *
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(pointcut = "logAspect()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        log.info("进入日志切面异常通知!!");
        log.info("异常信息: {}" + e.getMessage());
        e.printStackTrace();
        OperationLog operationLog = logThreadLocal.get();
        if (operationLog != null) {
            operationLog.setType("error");
            operationLog.setThrowingMsg(e.getMessage());
            new UpdateLogThread(operationLog, operationLogDao).start();
        }
    }

    private void process(JoinPoint joinPoint, String str, OperationLog operationLog, boolean afterReturn) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        OpLog annotation = methodSignature.getMethod().getAnnotation(OpLog.class);
        String type = annotation.description();
        OperationLogContext operationLogContext = new OperationLogContext(type);
        if (!afterReturn) {
            Object request = operationLogContext.processRequestParam(str);
            operationLog.setRequest(request);
        } else {
            Object response = operationLogContext.processResponse(str, operationLog);
            operationLog.setResponse(response);
        }
    }

    /**
     * 保存日志线程
     */
    private static class SaveLogThread implements Runnable {
        private final OperationLog operationLog;
        private final OperationLogDao operationLogDao;

        public SaveLogThread(OperationLog operationLog, OperationLogDao operationLogDao) {
            this.operationLog = operationLog;
            this.operationLogDao = operationLogDao;
        }

        @Override
        public void run() {
            operationLogDao.saveOrUpdate(operationLog);
        }
    }

    /**
     * 日志更新线程
     */
    private static class UpdateLogThread extends Thread {
        private final OperationLog operationLog;
        private final OperationLogDao operationLogDao;

        public UpdateLogThread(OperationLog operationLog, OperationLogDao operationLogDao) {
            super(UpdateLogThread.class.getSimpleName());
            this.operationLog = operationLog;
            this.operationLogDao = operationLogDao;
        }

        @Override
        public void run() {
            log.info("保存operationLog信息： {}", operationLog.toString());
            this.operationLogDao.saveOrUpdate(operationLog);
        }
    }
}